Next.js & React Server Comopnent で revalidation する
再描画する方法と、再取得している間にloading表示を出す方法
単純にrefreshする。太古のwebかな?と思わせるようなインターフェースになってて面白い。
next/navigation の方の useRouter を使う点に注意。
code:ClientComponent.tsx
import { useRouter } from 'next/navigation'
export default function LikeButton(props: { id: string }) {
const router = useRouter()
const like = async () => {
await post(/item/${props.id}/like)
router.refresh()
}
return (
<Button onClick={() => void like()}>Like</Button>
)
}
通信中にloading表示をしたい場合、こうしてみるが、これだとうまくいかない。postの間はloadingが表示されるが、router.refresh によって開始されたRSCの再取得を待っている間はloading表示されない。
code:ClientComponent.tsx
export default function LikeButton(props: { id: string }) {
const router = useRouter()
const like = async () => {
setLoading(true)
try {
await post(/item/${props.id}/like)
router.refresh()
} finaly {
setLoading(false)
}
}
return (
<Button onClick={() => void like()} spinner={loading}>Like</Button>
)
}
ここで router.refreshがPromiseならいいのにと思うのだが、実はより一貫性のある実装がなされている。
このrefreshのやっていることを考えると描画用のデータを取得しつつ描画できるまでの間はSuspendする動きのはずだ。しかしながら別にSuspenseのフォールバックが表示されるわけではなく、データが来るまでの間は描画は変化しない。これはReactのトランジションが開始されているためである。トランジションとしてマークされたステート変化は、Suspenseが解決されるまでの間は一時的に旧ステートのまま描画される。
実際にNext.jsのコードを見てみると React.startTransition を利用して再描画をトリガしている。
つまりコンポーネント内部で useTransition を利用して isPending のフラグをもらいつつ改めて startTransition すれば良さそうだ。startTransition 自体はrender priorityを付与するだけなので、ネストすることは自体は問題ない。
code:ClientComponent.tsx
export default function LikeButton(props: { id: string }) {
const router = useRouter()
const like = async () => {
setLoading(true)
try {
await post(/item/${props.id}/like)
startTransition(() => {
router.refresh()
})
} finaly {
setLoading(false)
}
}
return (
<Button onClick={() => void like()} spinner={loading || isPending}>Like</Button>
)
}
ちょっと感動した。Reactはうまいこと出来ている。
このコードを見ていると loading と isPending をいい感じにマージして使えるような useAsyncTransition みたいなものがほしい気がしてくるが、まあいったんここで〆ようと思う。